#include "stdafx.h"
#include <MyControls/Action.h>
#include <MyControls/Doc.h>
#include <MyControls/App.h>
#include <boost/mem_fn.hpp>
#include <xtd/functional>
#include <boost/bind.hpp>


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

Action::Action() : 
	m_ModifyInc(+1) 
{ 
}

Action::~Action()
{
	xtd::for_each(m_SubActions, xtd::deleter());
}

// MultiActionImpl

MultiActionImpl::MultiActionImpl(const String & caption) : 
	m_Caption(caption)
{
}

MultiActionImpl::~MultiActionImpl()
{
}

String MultiActionImpl::GetCaption() const
{
	if (!m_Caption.empty())
		return m_Caption;
	if (!m_SubActions.empty())
		return m_SubActions.front()->GetCaption();
	return String();
}

// MultiAction

MultiAction::MultiAction(const String & caption)
{
	GetApp().GetDoc().GetActions().BeginMultiAction(caption);
}

MultiAction::~MultiAction()
{
	GetApp().GetDoc().GetActions().EndMultiAction();
}

// Block

class ActionQueue::Block {
public:
	Block(ActionQueue & queue) : m_Queue(queue) {
		++m_Queue.m_BlockCount;
	}
	~Block() {
		--m_Queue.m_BlockCount;
	}
private:
	ActionQueue & m_Queue;
};


// ActionQueue

ActionQueue::ActionQueue(Doc & doc)
	: m_Owner(doc)
{
	m_Capacity = 0;
	m_Position = 0;
	m_ModifyCount = 0;
	m_BlockCount = 0;
}

void ActionQueue::SetCapacity(unsigned int cap)
{
	if (cap != 0)
		while (m_Deque.size() > cap) {
			delete m_Deque.back();
			m_Deque.pop_back();
		}
	m_Capacity = cap;
	if (cap != 0)
		if (m_Position > cap)
			m_Position = cap;
}

void ActionQueue::Clear()
{
	while (!m_Deque.empty()) {
		delete m_Deque.back();
		m_Deque.pop_back();
	}
	m_Position = 0;
	NonModified();
	Changed();
}
	
void ActionQueue::Push(Action * pAction)
{
	m_ModifyCount += pAction->m_ModifyInc;
	assert(m_ModifyCount >= 0);
	if (m_Position != 0) {
		for (unsigned int i = 0; i < m_Position; ++i)
			delete Pop();
	}
	m_Position = 0;
	m_Deque.push_front(pAction);
	if (m_Capacity != 0)
		if (m_Deque.size() > m_Capacity) {
			delete m_Deque.back();
			m_Deque.pop_back();
		}
}

Action * ActionQueue::Pop()
{
	Action * result = m_Deque.front();
	m_Deque.pop_front();
	return result;
}

void ActionQueue::Changed()
{
	m_Owner.DispatchEvent(Doc::ActionsChange(m_Owner));
}

bool ActionQueue::Perform(Action * pAction)
{
	if (m_BlockCount != 0) {
		delete pAction;
		return false;
	}
	bool res;
	try {
		m_Active.push(pAction);
		res = pAction->Execute();
		m_Active.pop();
	} catch(...) {
		delete pAction;
		throw;
	}

	if (res) {
		if (!m_Active.empty()) {
			m_Active.top()->m_SubActions.push_back(pAction);
		} else {
			// Push the action to the main queue
			Push(pAction);
			Changed();
		}
	} else
		delete pAction;

	return res;
}

void ActionQueue::Unexecute(Action * pAction)
{
	Block block(* this);
	std::for_each(pAction->m_SubActions.rbegin(), pAction->m_SubActions.rend(),
		boost::bind(&ActionQueue::Unexecute, this, _1));
	pAction->Unexecute();
}

void ActionQueue::Reexecute(Action * pAction)
{
	Block block(* this);
	pAction->Reexecute();
	xtd::for_each(pAction->m_SubActions, boost::bind(&ActionQueue::Reexecute, this, _1));
}

void ActionQueue::Undo()
{
	if (!m_Active.empty()) {
		assert(false);
		return;
	}
	if (m_Position < Size()) {
		Action * pAction = m_Deque[m_Position];
		m_Position++;
		Unexecute(pAction);
		m_ModifyCount -= pAction->m_ModifyInc;
		assert(m_ModifyCount >= 0);
		Changed();
	}
}

void ActionQueue::Redo()
{
	if (!m_Active.empty()) {
		assert(false);
		return;
	}
	if (m_Position > 0) {
		Action * pAction = m_Deque[m_Position - 1];
		m_Position--;
		Reexecute(pAction);
		m_ModifyCount += pAction->m_ModifyInc;
		assert(m_ModifyCount >= 0);
		Changed();
	}
}

void ActionQueue::NonModified()
{
	m_ModifyCount = 0;
	for (unsigned int i = m_Position; i < m_Deque.size(); ++i)
		m_Deque[i]->m_ModifyInc = - m_Deque[i]->m_ModifyInc;
	Changed();
}

void ActionQueue::Modified()
{
	m_ModifyCount++;
	Changed();
}

bool ActionQueue::IsModified() const
{
	return m_ModifyCount > 0;
}

void ActionQueue::BeginMultiAction(const String & caption)
{
	m_Active.push(new MultiActionImpl(caption));
}

void ActionQueue::EndMultiAction()
{
	assert(!m_Active.empty());
	Action * pAction = m_Active.top();
	m_Active.pop();

	if (!m_Active.empty()) {
		m_Active.top()->m_SubActions.push_back(pAction);
	} else {
		// Push the action to the main queue
		Push(pAction);
		Changed();
	}
}

} // My